feat(rest): let a tool see the response headers it asks for (Link pagination, rate limits) - #558
Merged
Merged
Conversation
A REST tool got the body and nothing else. Right for almost every call, wrong for list endpoints that paginate through a Link header (GitHub, GitLab, Sentry, Shopify): the model could pass a cursor in but never learned the next one, so every such tool was one page long. Rate-limit headers were the other thing adapters kept wishing they could show. A tool now opts in with endpointMapping.exposeHeaders, a list of header names. The engine returns those (lower-cased) next to the body through a new executeWithMeta; execute() is unchanged and every tool that did not opt in stays on it. The executor attaches them to the result as _headers, and reads Link for rel="next" into _pagination with nextUrl and, when the URL carries a recognisable parameter, nextCursor and its name. _pagination is absent on the last page: absence is the signal. Placement: after the response transform, so a select cannot drop them; an object body is extended, anything else is wrapped as data. The audit log keeps the bare body. The cache stores body and headers together in a small envelope and still reads entries written before it existed. Closes #557
The tool test page went through executeConnectorCall, which only ever saw the body, so a tool that asked for headers looked different there than to a model. Same helper, same shape.
GitHub's next link carries both after= and page=. The generic list chose after, but a tool mapping page: "$page" cannot send that back. The parameters a tool feeds from its own inputs now come first, so nextCursor is a value the model can pass straight back. Seen against the live API while testing locally.
Merged
keysersoft
added a commit
to Nagharjun17/anythingmcp
that referenced
this pull request
Sep 17, 2026
Finishing @Nagharjun17's adapter so it can go in. Their tools, descriptions and live spec; these are the three changes from the review. - SENTRY_ORG becomes an env var and goes into every path as {{SENTRY_ORG}}, so the model no longer has to know the organization slug on each call and cannot get it wrong. The parameter is gone from all four tools. - The three list tools opt into exposeHeaders, which HelpCode-ai#558 made possible after this PR was opened. Sentry paginates with a Link header, so each response now carries _pagination and the cursor descriptions say to follow nextCursor until it disappears. Before this, every list tool was exactly one page long, which the instructions had to apologise for. - The quoted adapter count moves 190 to 191 in the seven files the CI gate checks, which a new adapter always has to do. The base URL stays sentry.io rather than becoming {{SENTRY_BASE_URL}} as I first suggested: an optional placeholder that nobody fills in would be sent to the vendor as the literal string, which is a failure mode we hit elsewhere today. Self-hosters edit the connector's base URL after installing, and the instructions now say so. The live spec substitutes {{SENTRY_ORG}} itself, since it drives the engine directly and interpolation happens a layer above.
1 task
keysersoft
added a commit
that referenced
this pull request
Sep 17, 2026
* Add Sentry adapter with four read-only tools * feat(sentry): org as an env var, real pagination, count bumps Finishing @Nagharjun17's adapter so it can go in. Their tools, descriptions and live spec; these are the three changes from the review. - SENTRY_ORG becomes an env var and goes into every path as {{SENTRY_ORG}}, so the model no longer has to know the organization slug on each call and cannot get it wrong. The parameter is gone from all four tools. - The three list tools opt into exposeHeaders, which #558 made possible after this PR was opened. Sentry paginates with a Link header, so each response now carries _pagination and the cursor descriptions say to follow nextCursor until it disappears. Before this, every list tool was exactly one page long, which the instructions had to apologise for. - The quoted adapter count moves 190 to 191 in the seven files the CI gate checks, which a new adapter always has to do. The base URL stays sentry.io rather than becoming {{SENTRY_BASE_URL}} as I first suggested: an optional placeholder that nobody fills in would be sent to the vendor as the literal string, which is a failure mode we hit elsewhere today. Self-hosters edit the connector's base URL after installing, and the instructions now say so. The live spec substitutes {{SENTRY_ORG}} itself, since it drives the engine directly and interpolation happens a layer above. --------- Co-authored-by: Matteo <keysersoft@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #557. Came out of reviewing the Sentry adapter (#551).
What changes
endpointMapping.exposeHeaders: string[](REST only, opt in per tool). Names are matched case-insensitively and returned lower-cased.RestEngine.executeWithMeta()returns{ body, headers };execute()is now a thin wrapper and behaves exactly as before. Tools withoutexposeHeadersnever touch the new path._headers, and parses aLinkheader'srel="next"into_pagination: { nextUrl, nextCursor?, cursorParam?, prevUrl? }. Nonextrelation → no_paginationat all, which is the "last page" signal.selectcan't drop them); an object body is extended, anything else is wrapped asdata.docs/tool-definition.md.Not in this PR: updating existing adapters to use it. Sentry (#551), GitHub and Shopify are the obvious candidates once this is in.
Tests:
response-headers.util.spec(8),rest.engine.spec+2,dynamic-mcp-tools.spec+8 (opt-in vs not, last page, non-object body, transform ordering, audit excludes headers, cache round trip incl. legacy entries). mcp-server + engines suites: 336 green. tsc + eslint clean.